﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SpeakupBackend.Themes;
using SpeakupBackend.Classes;
using TableParsers;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SpeakupTests.SpeakupBackend.Themes
{
    [TestClass]
    public class ThemeBaseTests
    {
        [Serializable]
        class ThemeUnderTest: ThemeBase
        {
            public override bool IsCompatibleClass(Class cl)
            {
                //any class is assignable
                return true;
            }
        }

        Class CreateClass(ClassStatus status)
        {
            ClassId++;
            return CreateClass (status, ClassId);
        }

        Class CreateClass(ClassStatus status, int id)
        {            
            ClassName name = new ClassName("testClass", "test", ClassType.Club);
            ClassTime time = new ClassTime(DateTime.Now, TimeSpan.FromHours(10), TimeSpan.FromHours(12));
            return new Class(id, name, time, 13, status, ClassAction.None);
        }

        int ClassId = 0;

        [TestInitialize]
        public void TestInitialize()
        {
            ClassId = 0;
        }


        [TestMethod]
        public void ThemeBaseAddClass()
        {
            ThemeUnderTest theme = new ThemeUnderTest();
            
            //Initially all themes are "unettended"
            Assert.IsFalse(theme.IsAttended);

            //Tests that for all classes that are not of "Attended" status
            //does not change Theme 
            foreach (ClassStatus ct in Enum.GetValues(typeof(ClassStatus)))
            {
                if (ct != ClassStatus.Attended)
                {
                    Assert.IsTrue(theme.AddClass(CreateClass(ct)));
                    Assert.IsFalse(theme.IsAttended);
                }
            }

            //Test that class with "Attended" status changes
            //theme state to IsAttended
            Assert.IsTrue(theme.AddClass(CreateClass(ClassStatus.Attended)));
            Assert.IsTrue(theme.IsAttended);
        }

        [TestMethod]
        public void ThemeBaseClearClasses()
        {
            ThemeUnderTest theme = new ThemeUnderTest();

            //make theme be IsAttended
            Assert.IsTrue(theme.AddClass(CreateClass(ClassStatus.Attended)));
            Assert.IsTrue(theme.IsAttended);

            theme.ClearClasses();
            Assert.IsFalse(theme.IsAttended);
        }


        class Counter
        {
            public int CallCount;
        }

        [TestMethod]
        public void ThemeBasePropertyChanges()
        {
            Counter counter = new Counter();

            ThemeUnderTest theme = new ThemeUnderTest();
            theme.PropertyChanged += (s, e) => { counter.CallCount++; };

            //verify that no notification is sent if IsAttended is not changed
            foreach (ClassStatus ct in Enum.GetValues(typeof(ClassStatus)))
            {
                if (ct != ClassStatus.Attended)
                {
                    counter.CallCount = 0;
                    
                    Assert.IsTrue(theme.AddClass(CreateClass(ct)));
                    Assert.IsFalse(theme.IsAttended);
                    
                    Assert.AreEqual(0, counter.CallCount);                    
                }
            }            

            counter.CallCount = 0;
            {
                Assert.IsTrue(theme.AddClass(CreateClass(ClassStatus.Attended)));
                Assert.IsTrue(theme.IsAttended);
            }
            Assert.AreEqual(1, counter.CallCount);

            counter.CallCount = 0;
            {
                theme.ClearClasses();
                Assert.IsFalse(theme.IsAttended);
            }
            Assert.AreEqual(1, counter.CallCount);
        }

        class ThemeBaseBadClass:ThemeBase
        {
            public override bool IsCompatibleClass(Class cl)
            {
                return false;
            }
        }

        [TestMethod]
        public void ThemeBaseAddInvalidClass()
        {
            ThemeBaseBadClass theme = new ThemeBaseBadClass();

            bool thrown = false;
            try
            {
                theme.AddClass(CreateClass(ClassStatus.Attended));
            }
            catch (ArgumentException)
            {
                thrown = true;
            }

            Assert.AreEqual(true, thrown);
        }

        [TestMethod]
        public void ThemeBaseGetClasses()
        {
            ThemeUnderTest theme = new ThemeUnderTest();

            List<Class> classes = new List<Class>();
            

            foreach (ClassStatus ct in Enum.GetValues(typeof(ClassStatus)))
            {
                classes.Add(CreateClass(ct));
            }

            foreach (Class cl in classes)
            {
                Assert.IsTrue(theme.AddClass(cl));
            }

            foreach (Class cl in classes)
            {
                Assert.IsFalse(theme.AddClass(cl));
            }            

            Assert.AreEqual(classes.Count, theme.Classes.Count);
            foreach (Class cl in theme.Classes)
            {
                Assert.IsTrue(classes.Contains(cl));
            }
  
        }

        [TestMethod]
        public void ThemeBaseOnClassPropertyChanges()
        {
            Class cl1 = CreateClass(ClassStatus.Available);
            Class cl2 = CreateClass(ClassStatus.Missed);            
            Class cl3 = CreateClass(ClassStatus.Reserved);
            
            PrivateObject pcl1 = new PrivateObject(cl1);
            PrivateObject pcl2 = new PrivateObject(cl2);
            PrivateObject pcl3 = new PrivateObject(cl3);

            ThemeUnderTest theme = new ThemeUnderTest();

            Assert.IsTrue(theme.AddClass(cl1));
            Assert.IsTrue(theme.AddClass(cl2));
            Assert.IsTrue(theme.AddClass(cl3));
            
            //none of the classes had "Attended" status, so theme must be "NotAttended"
            Assert.IsFalse(theme.IsAttended);
            
            //non-"attended" class changed its status to something other then "attended".
            //theme still must be "NotAttended"
            pcl1.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsFalse(theme.IsAttended);

            //now non-"attended" class changed its status to attended/
            //theme must become "attended"
            pcl1.SetProperty("CurrentStatus", ClassStatus.Attended);
            Assert.IsTrue(theme.IsAttended);

            //now the only "attended" class changed it's status to non-"attended"
            //theme must become "NotAttended"
            pcl1.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsFalse(theme.IsAttended);

            //now all classes change theis status to "attended"
            pcl1.SetProperty("CurrentStatus", ClassStatus.Attended);
            pcl2.SetProperty("CurrentStatus", ClassStatus.Attended);
            pcl3.SetProperty("CurrentStatus", ClassStatus.Attended);
            Assert.IsTrue(theme.IsAttended);


            //now changing each class status to not attended one by one
            pcl1.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsTrue(theme.IsAttended);

            pcl2.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsTrue(theme.IsAttended);

            pcl3.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsFalse(theme.IsAttended);


            //make sure that after classes are removed from 
            //theme, theme's "IsAttended" is no longer depends on
            //those classess statuses.
            theme.ClearClasses();
            Assert.IsFalse(theme.IsAttended);

            pcl1.SetProperty("CurrentStatus", ClassStatus.Attended);
            pcl2.SetProperty("CurrentStatus", ClassStatus.Attended);
            pcl3.SetProperty("CurrentStatus", ClassStatus.Attended);
            Assert.IsFalse(theme.IsAttended);
        }

        [TestMethod]
        public void ThemeBaseSerializeDeserialize()
        {
            ThemeUnderTest theme = new ThemeUnderTest();
            Class cl = CreateClass(ClassStatus.Attended);

            theme.AddClass(cl);

            Assert.IsTrue(theme.IsAttended);
            {
                //seiralize and restore
                MemoryStream stream = new MemoryStream();
                BinaryFormatter formatter = new BinaryFormatter();

                formatter.Serialize(stream, theme);
                stream.Seek(0, SeekOrigin.Begin);

                theme = (ThemeUnderTest)formatter.Deserialize(stream);
            }
            Assert.IsTrue(theme.IsAttended);

            Assert.AreEqual(1, theme.Classes.Count);
            cl = theme.Classes.First();

            PrivateObject pcl = new PrivateObject(cl);
            
            pcl.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsFalse(theme.IsAttended);

            pcl.SetProperty("CurrentStatus", ClassStatus.Attended);
            Assert.IsTrue(theme.IsAttended);

            pcl.SetProperty("CurrentStatus", ClassStatus.Missed);
            Assert.IsFalse(theme.IsAttended);
        }

    }
}
